#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//
#include <fuse.h>
#include <fuse_opt.h>
//
#include <cxutil/logging.h>
#include <cxutil/utility.h>
//
#include "cpfs.h"
//
#include "cpfsfuse.h"

static void print_usage()
{
    fprintf(stderr,
"\n"
"cpfs-0.1 Cross Platform Filesystem\n"
"Copyright (C) 2010 Matthew Joiner <anacrolix@gmail.com>\n"
"\n"
"Usage: %s <device|image_file> <mountpoint>\n"
"\n"
            , program_invocation_short_name);
    char *argv[] = {program_invocation_name, "-ho", NULL};
    fuse_main(2, argv, NULL, NULL);
}

struct CpfsFuseConf {
    char const *devpath;
};

enum {
    KEY_HELP,
};

static int opt_proc(
        void *data,
        char const *arg,
        int key,
        struct fuse_args *OMIT_PARAM)
{
    struct CpfsFuseConf *conf = data;
    switch (key)
    {
    case FUSE_OPT_KEY_NONOPT:
        // take the first nonopt
        if (!conf->devpath) {
            conf->devpath = arg;
            return 0;
        } // pass other opts into fuse
        else {
            return 1;
        }
        return -1;
    case KEY_HELP:
        print_usage();
        exit(0);
        return -1;
    default:
        // just pass them on to fuse
        return 1;
    }
}

int main(int argc, char *argv[])
{
    int mainret = 1;

    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);

    struct CpfsFuseConf conf = {};

    // these options are hooked to replace fuse's
    struct fuse_opt const cpfs_fuse_opts[] = {
        FUSE_OPT_KEY("-h", KEY_HELP),
        FUSE_OPT_KEY("--help", KEY_HELP),
        FUSE_OPT_END,
    };

    if (0 != fuse_opt_parse(&args, &conf, cpfs_fuse_opts, opt_proc)) {
        fprintf(stderr, "%s", "Error parsing options\n");
        goto out;
    }
    if (!conf.devpath) {
        fprintf(stderr, "Missing device path parameter\n");
        mainret = 2;
        goto out;
    }

    // set default options (insert them at the beginning)

    //set the fsname (usually the device path, see the fuse readme)
    char const fsnamepfx[] = "-ofsname=";
    char *arg = malloc(strlen(fsnamepfx) + strlen(conf.devpath) + 1);
    strcpy(arg, fsnamepfx);
    strcat(arg, conf.devpath);
    fuse_opt_insert_arg(&args, 1, arg);
    free(arg);

    // set fs type (3rd mtab field, see the fuse readme)
    fuse_opt_insert_arg(&args, 1, "-osubtype=cpfs");

    // there's no reason not to pass this, ino's are completely unique in cpfs
    // also this is passed by ntfs-3g, if it's good enough for them it's good
    // enough for me
    fuse_opt_insert_arg(&args, 1, "-ouse_ino");

    // caching is not single thread safe
    fuse_opt_insert_arg(&args, 1, "-s");

    struct CpfsFuseData cfdata = {};
    cfdata.cpfs = cpfs_load(conf.devpath, -1, -1);
    if (!cfdata.cpfs) {
        LOG(Critical, "Error loading filesystem\n");
        goto out;
    }

    LOG(Info, "Arguments to fuse:");
    for (int i = 0; i < args.argc; ++i) {
        LOGF(Info, "\t%s", args.argv[i]);
    }
    mainret = fuse_main(args.argc, args.argv, &cpfs_oper, &cfdata);
    if (mainret) {
        LOGF(Critical, "FUSE main returned %d", mainret);
        goto out_loaded;
    }

out_loaded:
    if (cfdata.cpfs && !cpfs_unload(cfdata.cpfs)) {
        LOG(Critical, "Error unloading filesystem");
        // fuse_main worked but now unloading failed
        if (!mainret)
            mainret = 1;
    }
out:
    return mainret;
}
